package com.demo.wrapper;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.demo.wrapper.enums.BoolEnum;
import com.demo.wrapper.enums.QueryEnum;
import com.demo.wrapper.enums.ScoreModeEnum;
import com.demo.wrapper.interfaces.*;
import com.demo.wrapper.pojo.Arg;

import java.util.*;
import java.util.function.Consumer;

/**
 * es搜索拼接抽象类
 *
 * @param <Wrapper>    AbstractEsQueryWrapper的子类
 * @param <AggWrapper> 聚合拼接类型
 * @author 19242
 */
public abstract class AbstractEsQueryWrapper<Wrapper extends AbstractEsQueryWrapper<Wrapper, AggWrapper, Query, Source>, AggWrapper extends IEsAggWrapper<AggWrapper, Wrapper>, Query, Source>
        implements IEsQueryWrapper<Wrapper>, IEsRelationWrapper<Wrapper>, IEsLimitWrapper<Wrapper, AggWrapper>, IBuilderWrapper<Query, Source> {
    /**
     * 当前类 this
     */
    private final Wrapper child;
    /**
     * 关系类型
     */
    private BoolEnum boolEnum;
    /**
     * 搜索条件
     */
    private Set<Arg> querySet;
    /**
     * 子条件
     */
    private Map<BoolEnum, List<Wrapper>> children;
    /**
     * 限制条件
     */
    private List<Arg> limitList;

    public AbstractEsQueryWrapper() {
        boolEnum = BoolEnum.MUST;
        child = (Wrapper) this;
    }

    /**
     * 切换查询关系
     *
     * @param boolEnum
     * @return
     */
    @Override
    public Wrapper check(BoolEnum boolEnum) {
        // 关系相同，不变
        if (boolEnum == getBoolEnum()) {
            return child;
        }
        // 将之前收集到的查询放入子查询里
        Wrapper wrapper = instanceChild();
        wrapper.setBoolEnum(getBoolEnum());
        wrapper.addAllQuery(getQuerySet(false));
        addChildren(wrapper);
        // 清空当前查询
        clearQuery();
        // 使用新关系
        setBoolEnum(boolEnum);
        return child;
    }


    @Override
    public Wrapper or(Consumer<Wrapper> consumer) {
        Wrapper wrapper = instanceChild();
        // 初始化关系
        wrapper.setBoolEnum(BoolEnum.SHOULD);
        consumer.accept(wrapper);
        // 如果wrapper改变了关系，改回来
        wrapper.check(BoolEnum.SHOULD);
        return addChildren(wrapper);
    }

    @Override
    public Wrapper and(Consumer<Wrapper> consumer) {
        Wrapper wrapper = instanceChild();
        // 初始化关系
        wrapper.setBoolEnum(BoolEnum.MUST);
        consumer.accept(wrapper);
        // 如果wrapper改变了关系，改回来
        wrapper.check(BoolEnum.MUST);
        return addChildren(wrapper);
    }

    @Override
    public Wrapper not(Consumer<Wrapper> consumer) {
        Wrapper wrapper = instanceChild();
        // 初始化关系
        wrapper.setBoolEnum(BoolEnum.MUST_NOT);
        consumer.accept(wrapper);
        // 如果wrapper改变了关系，改回来
        wrapper.check(BoolEnum.MUST_NOT);
        return addChildren(wrapper);
    }

    @Override
    public Wrapper filter(Consumer<Wrapper> consumer) {
        Wrapper wrapper = instanceChild();
        // 初始化关系
        wrapper.setBoolEnum(BoolEnum.FILTER);
        consumer.accept(wrapper);
        // 如果wrapper改变了关系，改回来
        wrapper.check(BoolEnum.FILTER);
        return addChildren(wrapper);
    }

    @Override
    public Wrapper nested(String path, Consumer<Wrapper> consumer, ScoreModeEnum scoreMode) {
        Wrapper wrapper = instanceChild();
        consumer.accept(wrapper);
        // 对nest进行预处理，先提供
        addQuery(new Arg(QueryEnum.NESTED, null, null, null, path, wrapper, scoreMode));
        return child;
    }

    @Override
    public Wrapper addQuery(Arg arg) {
        if (Objects.nonNull(arg)) {
            getQuerySet(true).add(arg);
        }
        return child;
    }

    @Override
    public Wrapper clearQuery() {
        if (CollectionUtil.isNotEmpty(querySet)) {
            querySet.clear();
        }
        return child;
    }

    protected Wrapper addAllQuery(Collection<Arg> args) {
        if (CollectionUtil.isNotEmpty(args)) {
            getQuerySet(true).addAll(args);
        }
        return child;
    }

    /**
     * 获取条件集合
     *
     * @param instance 是否初始化集合
     * @return 条件集合
     */
    protected Set<Arg> getQuerySet(boolean instance) {
        if (instance && Objects.isNull(querySet)) {
            querySet = new LinkedHashSet<>();
        }
        return querySet;
    }

    protected BoolEnum getBoolEnum() {
        return boolEnum;
    }

    protected void setBoolEnum(BoolEnum boolEnum) {
        this.boolEnum = boolEnum;
    }

    /**
     * 清空子条件
     *
     * @return
     */
    protected Wrapper clearChildren() {
        if (MapUtil.isNotEmpty(children)) {
            children.clear();
        }
        return child;
    }

    /**
     * 获取子查询
     *
     * @param instance 是否初始化
     * @return 子查询map
     */
    protected Map<BoolEnum, List<Wrapper>> getChildren(boolean instance) {
        if (instance && Objects.isNull(children)) {
            children = new HashMap<>(8);
        }
        return children;
    }

    /**
     * 添加子查询
     *
     * @param wrapper 子查询
     * @return this
     */
    protected Wrapper addChildren(Wrapper wrapper) {
        if (Objects.isNull(wrapper)
                || (CollectionUtil.isEmpty(wrapper.getQuerySet(false)) && CollectionUtil.isEmpty(wrapper.getChildren(false)))) {
            return child;
        }
        Map<BoolEnum, List<Wrapper>> children = getChildren(true);
        List<Wrapper> childrenQueryList = children.get(wrapper.getBoolEnum());
        if (Objects.nonNull(childrenQueryList)) {
            childrenQueryList.add(wrapper);
            return child;
        }
        List<Wrapper> newChildrenQueryList = new ArrayList<>();
        newChildrenQueryList.add(wrapper);
        children.put(wrapper.getBoolEnum(), newChildrenQueryList);
        return child;
    }

    @Override
    public Wrapper reset() {
        clearQuery();
        clearChildren();
        setBoolEnum(BoolEnum.MUST);
        return child;
    }

    protected List<Arg> getLimitList(boolean instance) {
        if (instance && Objects.isNull(limitList)) {
            limitList = new ArrayList<>();
        }
        return limitList;
    }

    @Override
    public Wrapper addLimit(Arg arg) {
        getLimitList(true).add(arg);
        return child;
    }
}
